package org.jeecg.modules.device.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.zhouwr.common.device.vo.*;
import com.zhouwr.common.enums.FunctionParamInputMode;
import com.zhouwr.common.message.SendMessage;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.modules.device.entity.*;
import org.jeecg.modules.device.enums.DeviceInstanceState;
import org.jeecg.modules.device.enums.DeviceType;
import org.jeecg.modules.device.listener.event.InstanceOfflineEvent;
import org.jeecg.modules.device.mapper.DeviceInstanceMapper;
import org.jeecg.modules.device.quartzJob.InvokeFunctionJob;
import org.jeecg.modules.device.service.*;
import org.jeecg.modules.device.vo.InstanceParamStructure;
import org.jeecg.modules.network.network.NetworkConnect;
import org.jeecg.modules.network.network.NetworkConnectStore;
import org.jeecg.modules.scene.entity.Scene;
import org.jeecg.modules.scene.service.impl.SceneServiceImpl;
import org.quartz.*;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @Description: 设备实例
 * @Author: jeecg-boot
 * @Date: 2020-04-11
 * @Version: V1.0
 */
@Slf4j
@Service
public class DeviceInstanceServiceImpl extends ServiceImpl<DeviceInstanceMapper, DeviceInstance> implements IDeviceInstanceService {

    @Autowired
    private DeviceInstanceMapper instanceMapper;
    @Autowired
    private IDeviceInstanceParamService instanceParamService;
    @Autowired
    private ISysBaseAPI sysBaseAPI;
    @Autowired
    private IDeviceFunctionService functionService;
    @Autowired
    private IDeviceFunctionParamService functionParamService;
    @Autowired
    private IDeviceDataService dataService;
    @Autowired
    private IDataReportService reportService;
    @Autowired
    private SceneServiceImpl sceneService;

    @Autowired
    private Scheduler scheduler;
    @Autowired
    private ApplicationEventPublisher publisher;

    @Override
    public List<DeviceInstance> listInstanceDeviceByModelType(String modelType) {
        return instanceMapper.getInsDeviceByModelType(modelType);
    }

    @Override
    public Boolean createTable(String instanceId) {

        Map<String, String> map = new HashMap<>();
        this.buildInstanceDataCurrent(instanceId).forEach(data -> {
            map.put(data.getCode(), data.getType());
        });

        instanceMapper.createDataTable(instanceId, map);

        return true;
    }

    @Override
    public DeviceInstance getInstanceDeviceByAddress(String address) {
        final List<DeviceInstanceParam> instanceParams = instanceParamService.lambdaQuery()
                .eq(DeviceInstanceParam::getValue, address)
                .list();

        if (instanceParams == null) {
            log.warn("根据TCP地址获取不到设备实例！请将实例配置信息的本地地址设置为：" + address);
            return null;
        }
        if (instanceParams.size() < 1) {
            log.warn("根据TCP地址获取不到设备实例！请将实例配置信息的本地地址设置为：" + address);
            return null;
        }
        if (instanceParams.size() != 1) {
            log.warn("根据TCP地址获取到的设备实例不唯一！请将实例配置信息的本地地址设置为：" + address);
            return null;
        }
        return this.instanceMapper.selectById(instanceParams.get(0).getInstanceId());
    }

    @Override
    public List<DeviceData> getDeviceDatasByModelId(String modelId) {
        return dataService.lambdaQuery()
                .eq(DeviceData::getDeviceModelBy, modelId)
                .list();
    }

    @Override
    public List<DeviceData> getDeviceDatasByInstanceId(String instanceId) {
        DeviceInstance instance = this.getById(instanceId);
        return this.getDeviceDatasByModelId(instance.getModelBy());
    }

    @Override
    public DeviceInstanceState getNetworkState(String instanceId) {
        final DeviceInstance parentGateway = getParentGateway(instanceId);
        final boolean b = instanceParamService.lambdaQuery()
                .eq(DeviceInstanceParam::getInstanceId, parentGateway.getId())
                .list()
                .stream()
                .anyMatch(instanceParam -> NetworkConnectStore.getNetworkConnectMap().containsKey(instanceParam.getValue().toString()));
        if (b) {
            return DeviceInstanceState.ONLINE;
        } else {
            return DeviceInstanceState.OFFLINE;
        }
    }

    @Override
    public ChannelHandlerContext getNetworkChannel(String instanceId) throws Exception {
        long a = System.currentTimeMillis();
        DeviceInstance instance = getParentGateway(instanceId);
        if (DeviceInstanceState.NOT_ACTIVE.equals(instance.getStatus())) {
            throw new Exception("网关设备实例尚未激活！");
        } else if (DeviceInstanceState.OFFLINE.equals(instance.getStatus())) {
            throw new Exception("网关设备实例离线！");
        }
        final List<NetworkConnect> connects = NetworkConnectStore.getNetworkConnectMap().entrySet().stream().map(connect -> {
            log.info("getNetworkChannel >>>> {}", connect);
            try {
                Integer count = instanceParamService.lambdaQuery()
                        .eq(DeviceInstanceParam::getInstanceId, instanceId)
                        .eq(DeviceInstanceParam::getValue, connect.getKey())
                        .count();
                if (count == 1) {
                    return connect.getValue();
                } else if (count > 1) {
                    throw new Exception("网关地址配置重复！");
                } else {
                    throw new Exception("网关地址配置不存在！");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }).collect(Collectors.toList());
        log.info("耗时：{}", System.currentTimeMillis() - a);
        return connects.size() == 1 ? connects.get(0).getChannelHandlerContext() : null;
    }

    @Override
    public DeviceInstance getParentGateway(String instanceId) {
        DeviceInstance instance = this.getById(instanceId);
        DeviceModel model = instanceMapper.getDeviceModelByModelId(instance.getModelBy());
        /* 网关设备 或 直连设备，直接返回 */
        if (model.getType().equals(DeviceType.GATEWAY) || model.getType().equals(DeviceType.DIRECT_TERMINAL)) {
            return instance;
        } else {
            if (StringUtils.isEmpty(instance.getParentBy())) {
                throw new RuntimeException("找不到父级网关设备，请联系管理员！");
            }
            return getParentGateway(instance.getParentBy());
        }
    }

    @Override
    public DeviceInstance getParentInstance(String instanceId) {
        DeviceInstance instance = this.getById(instanceId);
        if (StringUtils.isNotBlank(instance.getParentBy())) {
            instance = instanceMapper.selectById(instance.getParentBy());
        }
        return instance;
    }

    @Override
    public void setFuncExecConf(InstanceFunctionExecStructure execStructure) {
        String deviceInstanceId = execStructure.getInstanceId();
        DeviceInstance instance = this.getById(deviceInstanceId);
        instance.setFuncExecConf(JSON.toJSONString(execStructure.getExecConfig()));
        log.info("更新执行配置：{}，成功：{}", JSON.toJSONString(execStructure.getExecConfig()), this.updateById(instance));
    }

    /**
     * 删除执行任务
     *
     * @param execStructure
     */
    @Override
    public void deleteExecJob(InstanceFunctionExecStructure execStructure) {
        try {
            String triggerKey = InvokeFunctionJob.class.getCanonicalName()
                    + "-" + execStructure.getInstanceId()
                    + "-" + execStructure.getFunctionCode();
            scheduler.pauseTrigger(TriggerKey.triggerKey(triggerKey));
            scheduler.unscheduleJob(TriggerKey.triggerKey(triggerKey));
            scheduler.deleteJob(JobKey.jobKey(triggerKey));
            // scheduler.clear();
        } catch (Exception e) {
            execStructure.getExecConfig().setRunning(false);
            this.setFuncExecConf(execStructure);
            e.printStackTrace();
        }
    }

    @Override
    public void addExecJob(InstanceFunctionExecStructure execStructure) throws Exception {
        try {
            String triggerKey = InvokeFunctionJob.class.getCanonicalName() + "-" + execStructure.getInstanceId() + "-" + execStructure.getFunctionCode();
            scheduler.start();

            List<SendMessage> sendMessages = buildFunctionExecStructure(getById(execStructure.getInstanceId()))
                    .stream()
                    .peek(System.out::println)
                    .map(InstanceFunctionExecStructure::toSendMessage)
                    .collect(Collectors.toList());
            // 构建job信息
            JobDetail jobDetail = JobBuilder
                    .newJob(InvokeFunctionJob.class)
                    .withIdentity(triggerKey)
                    .usingJobData("sendMessages", JSONArray.toJSONString(sendMessages))
                    .build();

            // 表达式调度构建器(即任务执行的时间)
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(execStructure.getExecConfig().getCorn());
            // 按新的cronExpression表达式构建一个新的trigger
            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
            scheduler.scheduleJob(jobDetail, trigger);
            this.setFuncExecConf(execStructure);
        } catch (Exception e) {
            execStructure.getExecConfig().setRunning(false);
            this.setFuncExecConf(execStructure);
            log.error(e.getMessage());
            throw new RuntimeException(e.getMessage());
        }
    }

    @Override
    public List<DeviceInstance> getInstanceChildren(String id, Boolean withSelf) {
        List<String> ids = Lists.newArrayList(id);
        List<DeviceInstance> instanceChildren = this.getInstanceChildren(ids);
        if (withSelf) {
            instanceChildren.add(this.getById(id));
        }
        return instanceChildren;
    }

    public List<DeviceInstance> getInstanceChildren(List<String> ids) {
        List<DeviceInstance> instances = this.lambdaQuery()
                .in(DeviceInstance::getParentBy, ids)
                .list();
        if (CollectionUtils.isNotEmpty(instances)) {
            List<String> instanceIds = instances.stream()
                    .map(DeviceInstance::getId)
                    .collect(Collectors.toList());
            instances.addAll(getInstanceChildren(instanceIds));
            return instances;
        } else {
            return Lists.newArrayList();
        }
    }

    @Override
    public List<InstanceExtentParams> getModelExtendParams(String instanceId, String modelId) throws Exception {
        // 1、根据deviceModelId 获取设备模型的功能定义
        return functionService.lambdaQuery()
                .eq(DeviceFunction::getDeviceModelBy, modelId)
                .list().stream()
                .map(function -> {
                    try {
                        return new InstanceExtentParams(
                                function.vo(),
                                this.buildFunctionInputParams(
                                        functionParamService.getFunctionParams(function.getId()),
                                        instanceParamService.getInstanceParams(instanceId, function.getId())
                                )
                        );
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return new InstanceExtentParams();
                })
                .collect(Collectors.toList());
    }

    @Override
    public List<InstanceExtentParams> getModelExtendParams(String modelId, FunctionParamInputMode inputMode) throws Exception {
        return functionService.lambdaQuery()
                .eq(DeviceFunction::getDeviceModelBy, modelId)
                .list().stream()
                .map(function -> {
                    try {
                        return new InstanceExtentParams(
                                function.vo(),
                                this.buildFunctionInputParams(
                                        functionParamService.getFunctionParams(function.getId()),
                                        inputMode
                                )
                        );
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return new InstanceExtentParams();
                })
                .collect(Collectors.toList());
    }

    @Override
    public List<InstanceExtentParams> getInstanceExtendParams(String instanceId) throws Exception {
        final List<DeviceInstanceParam> instanceParams = instanceParamService.lambdaQuery().eq(DeviceInstanceParam::getInstanceId, instanceId).list();
        DeviceInstance instance = instanceMapper.selectById(instanceId);
        if (instance == null) {
            throw new IllegalArgumentException("设备实例找不到：" + instanceId);
        }
        return functionService.selectByMainId(instance.getModelBy()).stream().map(function -> {
            List<InstanceFunctionInputParam> inputParams = null;
            try {
                inputParams = buildFunctionInputParams(functionParamService.getFunctionParams(function.getId()), instanceParams);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return new InstanceExtentParams(function.vo(), inputParams);
        }).collect(Collectors.toList());
    }

    /**
     * 功能输入参数，不带数值
     *
     * @param inputParams 功能输入参数
     * @return
     * @throws Exception
     */
    @Override
    public List<InstanceFunctionInputParam> buildFunctionInputParams(List<DeviceFunctionParam> inputParams) throws Exception {
        return inputParams.stream().map(functionParam -> {
            DeviceData data = dataService.getById(functionParam.getDataId());
            if (data == null) {
                throw new RuntimeException("设备数据节点：" + functionParam.getDataId() + "找不到，请检查配置！！！");
            }
            return new InstanceFunctionInputParam(data.vo(), functionParam.getInputMode());
        }).collect(Collectors.toList());
    }

    /**
     * 功能输入参数，不带数值
     *
     * @param inputParams
     * @param inputMode
     * @return
     * @throws Exception
     */
    @Override
    public List<InstanceFunctionInputParam> buildFunctionInputParams(List<DeviceFunctionParam> inputParams, FunctionParamInputMode inputMode) {
        try {
            return this.buildFunctionInputParams(inputParams).stream()
                    .filter(instanceFunctionInputParam -> instanceFunctionInputParam.getInputMode().equals(inputMode))
                    .collect(Collectors.toList());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 功能输入参数，带数值
     *
     * @param inputParams          功能输入参数
     * @param instanceExtendParams 实例扩展参数，对应功能输入参数
     * @return
     * @throws Exception
     */
    @Override
    public List<InstanceFunctionInputParam> buildFunctionInputParams(List<DeviceFunctionParam> inputParams, List<DeviceInstanceParam> instanceExtendParams) throws Exception {
        return inputParams.stream().map(functionParam -> {
            DeviceData data = dataService.getById(functionParam.getDataId());
            if (data == null) {
                throw new RuntimeException("设备数据节点：" + functionParam.getDataId() + "找不到，请检查配置！！！");
            }
            final List<Object> objects = instanceExtendParams
                    .stream()
                    .filter(extentParam -> functionParam.getDirection().getCode().equals(extentParam.getType().getCode()) &&
                            functionParam.getFunctionId().equals(extentParam.getFunctionId()) &&
                            functionParam.getDataId().equals(extentParam.getDataId()))
                    .map(DeviceInstanceParam::getValue).collect(Collectors.toList());

            return new InstanceFunctionInputParam(data.vo(), objects.size() > 0 ? objects.get(0) : null, functionParam.getInputMode());
        }).collect(Collectors.toList());
    }

    /**
     * 构建功能及输入参数结构
     *
     * @param instance 设备实例
     * @return 设备实例扩展参数（功能及输入参数）
     */
    @Override
    public List<InstanceFunctionStructure> buildFunctionStructures(DeviceInstance instance) throws Exception {
        // 根据实例id，获取设备实例参数
        final List<DeviceInstanceParam> instanceParams = instanceParamService.lambdaQuery().eq(DeviceInstanceParam::getInstanceId, instance.getId()).list();
        // 根据设备模型id，获取模型功能
        List<DeviceFunction> functions = functionService.selectByMainId(instance.getModelBy());
        return functions.stream().map(function -> {
            List<InstanceFunctionInputParam> inputParams = null;
            try {
                inputParams = this.buildFunctionInputParams(functionParamService.getFunctionParams(function.getId()), instanceParams);
            } catch (Exception e) {
                e.printStackTrace();
            }
            String funcExecConf = instance.getFuncExecConf();
            return new InstanceFunctionStructure(instance.getId(), function.vo(), inputParams, funcExecConf);

        }).collect(Collectors.toList());
    }

    @Override
    public List<InstanceDataStructure> buildInstanceDataHistory(String instanceId) {
        // 根据实例id，获取实例上报数据
        List<ReportDataVo> reportDataVos = reportService.lambdaQuery()
                .eq(DataReport::getInstanceId, instanceId)
                .list()
                .stream()
                // 转换成 ReportDataVo
                .map(DataReport::vo).collect(Collectors.toList());

        return this.getDeviceDatasByInstanceId(instanceId)  // 根据实例id，获取设备数据节点
                .stream()
                .map(deviceData -> {
                    final DeviceInstance instance = getById(instanceId);
                    return new InstanceDataStructure(instanceId, instance.getName(), deviceData.vo(), reportDataVos
                            .stream()
                            .filter(reportDataVo -> reportDataVo.getCode().equals(deviceData.getCode()))
                            .collect(Collectors.toList())
                    );
                }).collect(Collectors.toList());

    }

    @Override
    public List<InstanceDataStructure> buildInstanceDataHistory(String instanceId, Date begin, Date end) {
        List<ReportDataVo> reportDataVos = reportService.lambdaQuery()
                .eq(DataReport::getInstanceId, instanceId)
                .between(DataReport::getCreateTime, begin, end)
                .list()
                .stream()
                // 转换成 ReportDataVo
                .map(DataReport::vo).collect(Collectors.toList());

        return this.getDeviceDatasByInstanceId(instanceId)  // 根据实例id，获取设备数据节点
                .stream()
                .map(deviceData -> {
                    final DeviceInstance instance = getById(instanceId);
                    return new InstanceDataStructure(instanceId, instance.getName(), deviceData.vo(), reportDataVos
                            .stream()
                            .filter(reportDataVo -> reportDataVo.getCode().equals(deviceData.getCode()))
                            .collect(Collectors.toList())
                    );
                }).collect(Collectors.toList());

    }

    @Override
    public List<InstanceDataStructure> buildInstanceDataCurrent(String instanceId) {

        return this.getDeviceDatasByInstanceId(instanceId)  // 根据实例id，获取设备数据节点
                .stream()
                .map(deviceData -> {
                    DataReport dataReport = reportService.getReportDataLast(instanceId, deviceData.getCode());
                    final DeviceInstance instance = getById(instanceId);
                    return new InstanceDataStructure(instanceId, instance.getName(), deviceData.vo(), dataReport.vo());
                }).collect(Collectors.toList());
    }

    @Override
    public List<InstanceFunctionExecStructure> buildFunctionExecStructure(DeviceInstance instance) {
        // 根据实例id，获取设备实例参数
        final List<DeviceInstanceParam> instanceParams = instanceParamService.lambdaQuery().eq(DeviceInstanceParam::getInstanceId, instance.getId()).list();

        return instanceParams.stream().map(instanceParam -> {
            // 获取功能，根据id
            DeviceFunction function = functionService.getById(instanceParam.getFunctionId());
            if (function == null) {
                instanceParamService.removeById(instanceParam.getId());
                throw new RuntimeException("实例：" + instance.getName() + "的功能id：" + instanceParam.getFunctionId() + "不存在，系统自动删除实例参数:" + instanceParam.getId());
            }
            List<InstanceFunctionInputParam> inputParams = null;
            try {
                List<DeviceFunctionParam> functionParams = functionParamService.getFunctionParams(function.getId());
                inputParams = this.buildFunctionInputParams(functionParams, instanceParams);
            } catch (Exception e) {
                e.printStackTrace();
                log.warn(e.getMessage());
                throw new RuntimeException(e.getMessage());
            }
            final String funcExecConf = instance.getFuncExecConf();
            return new InstanceFunctionExecStructure(instance.getId(), function.vo(), inputParams, funcExecConf);
        }).collect(Collectors.toList());
    }

    @Override
    public void batchUpdateInstanceState(String instanceId, DeviceInstanceState status) {
        // 1、更新当前设备状态
        DeviceInstance currentInstance = this.getById(instanceId);
        if (!currentInstance.getStatus().equals(DeviceInstanceState.NOT_ACTIVE)) {
            currentInstance.setStatus(status);
            this.updateById(currentInstance);
        }

        // 2、查询子集设备
        this.lambdaQuery()
                .eq(DeviceInstance::getParentBy, instanceId)
                .ne(DeviceInstance::getStatus, DeviceInstanceState.NOT_ACTIVE)
                .list().forEach(instance -> {
            // 设置状态
            instance.setStatus(status);
            // 设置状态更新时间
            instance.setStatusUpdateTime(new Date());
            this.batchUpdateInstanceState(instance.getId(), instance.getStatus());
        });
    }

    @Override
    @Transactional
    public void updateInstanceParams(InstanceParamStructure structure) {
        final List<InstanceExtentParams> extendParams = structure.getExtendParams();
        final DeviceInstance instance = new DeviceInstance();
        BeanUtils.copyProperties(structure, instance);
        log.info(">>>> {}", instance);
        instance.setId(instance.getCode());
        // 更新实例对象
        this.updateById(instance);

        /* 同步下级数据：所属场景、所属方案、所属组织 */
        this.getInstanceChildren(instance.getId(), false).forEach(childInstance -> {
            childInstance.setSceneBy(instance.getSceneBy());
            childInstance.setSceneSchemeBy(instance.getSceneSchemeBy());
            childInstance.setSysOrgCode(instance.getSysOrgCode());
            this.updateById(childInstance);
        });

        // 更新实例参数
        // 先删除实例参数，根据实例id
        instanceParamService.removeByInstanceId(structure.getId());
        instanceParamService.saveParamBatch(structure.getId(), extendParams);
    }

    @Override
    public void manuallyOffline(String instanceId) {
        log.info("manuallyOffline >>>> {}", instanceId);
        final Map<String, NetworkConnect> connectMap = NetworkConnectStore.getNetworkConnectMap();
        final Set<String> set = connectMap.keySet();
        for (String key : set) {
            DeviceInstance instance = connectMap.get(key).getInstance();
            if (instance.getId().equals(instanceId)) {
                NetworkConnectStore.removeConnect(key);
                return;
            }
        }
        // 不是网关设备时，connectMap里没有实例，需手动触发事件
        publisher.publishEvent(new InstanceOfflineEvent(null, instanceId));
    }

    @Override
    public Scene getScene(String instanceId) {
        return sceneService.getById(this.getSceneId(instanceId));
    }

    @Override
    public String getSceneId(String instanceId) {
        return this.getById(instanceId).getSceneBy();
    }

    @Override
    public List<DeviceInstance> querySameLevelById(String instanceId) {
        DeviceInstance parentInstance = this.getParentInstance(instanceId);
        if (parentInstance == null) {
            throw new RuntimeException();
        }
        return this.lambdaQuery().eq(DeviceInstance::getParentBy, parentInstance.getId()).list();
    }
}
